1. Field of the Invention
The present invention relates generally to a computer implemented method, a computer program product, and a data processing system. More specifically, the present invention relates to a computer implemented method, a computer program product, and a data processing system for selective execution of trace mechanisms for applications having different bit structures.
2. Description of the Related Art
A probe or trace is a software mechanism that interrupts normal system action to investigate and obtain information about current context and system state. Tracing actions or probe actions refer to the actions performed by the trace. Typically, they include the capturing of information by writing the current values of global and context-specific information to a trace buffer. The obtained information, thus captured in the trace buffer, is called trace data. The system usually provides facilities to consume the trace, that is, read the data out of the trace buffer and make it available to the users of the system.
A trace point identifies a point during normal system activity that is capable of being traced. With dynamic tracing, trace points do not have any probes attached to them unless they are being traced. Enabling a trace is the operation of attaching a probe to a trace point and disabling a trace is the operation of removing a probe from a trace point. Triggering or firing of a probe refers to the condition where an enabled trace point occurs during system activity and the tracing actions are performed.
Dynamic tracing requires tracing actions to be executed dynamically. The tracing actions are often written in a programming language and usually compiled into machine code or byte code, which is then loaded into the Operating System. At the time a trace point is triggered, this machine code or byte code for the trace is executed, which allows for the efficient execution of trace actions. Attempting to interpret and execute trace actions at the time a trace point is triggered incurs the extra overhead involved in translating the code into machine code.
An important requirement for tracing is the ability to access the values of global variables, input parameters to a function being traced, return values from functions being traced and other context data at the trace point in an application that is being traced. There are two common data models available for applications on most modern operating systems: the 32-bit and the 64-bit data models. Accordingly, compilers on AIX and other operating systems, support converting a program written in the high-level language into either a 32-bit or 64-bit binary executable. Dynamic tracing must support tracing both 32-bit and 64-bit applications.
Because 32-bit applications and 64-bit applications follow different data models, a problem arises when the same tracing actions code needs to be executed for both 32-bit and 64-bit applications and context or global data in the application must be accessed as part of executing the tracing actions. This problem is specific to compilers that need to generate object code that can be executed in one or more data modes. Standard compilers generating either 32-bit object code to create a 32-bit executable, or generating 64-bit object code to create a 64-bit executable will not experience this problem, although separate 32-bit and 64-bit executables may be created from the same program.
For example, pointers and long variables (“longs”) in a 32-bit application assume different sizes than do pointers and longs in a 64-bit application. Consider the case where we put a trace point at entry into a system call, such as “sigaction.” The sigaction system call takes a pointer to the sigaction data structure which has the following structure layout (shown here in C language format):
struct sigaction {  void  (*sa_handler (int, siginfo_t *, void *);  /* Signal handler */  sigset_t sa_mask;   /* signals to block while in handler */  int sa_flags;  /* signal action flags */};
For a 32-bit application, the sa_handler field is 4 bytes long. The sa_mask field therefore starts at offset 4. However, for a 64-bit application, the sa_handler field is 8 bytes long. The sa_mask field for a 64-bit application therefore starts at offset 8.
Consider the following example of a dynamic tracing program that traces the sigaction system call:
int sigaction(int signo, struct sigaction *act, struct sigaction *oact);@@syscall:*:sigaction:entry    /* Probe the sigaction system call */{  struct sigaction *act;  /* Copy the data passed by the user into our internal data structure,  * we use _arg2 because the sigaction structure is the  second argument  */  act = get_userdata(_arg1, sizeof(struct sigaction));  printf(“Process %d calls sigaction for signal %d with flags %d\n”,    _pid, _arg1, act->sa_flags);}In this example, the offset generated in the object code by the compiler for the sa_flags field needs to be different depending upon whether the process or application being traced is 32-bit or 64-bit.
Consider the case of the sizeof( ) operator which returns the size of a variable In AIX, a “unsigned long” variable in a 64-bit application can contain values up to 264 bits and a “unsigned long” variable in a 64-bit application is 8 bytes long. However, for a 32-bit application, a “unsigned long” variable can contain only values up to 232 bits and the size of the “unsigned long” variable is therefore 4-bytes. The compiler must somehow generate code that returns 4 bytes or 8 bytes depending upon the process that is being traced.
Existing solutions that vary offsets, sizes of variables, etc. depending on whether a 32-bit or 64-bit application is executing the (tracing) code include the following.
The first solution involves creating two tracing modules. The first module is compiled for 32-bit mode having matching 32-bit long/pointer sizes and the second module is compiled for the 64-bit mode having matching 64-bit long/pointer sizes. The user uses the 32-bit module if the applications being traced is 32-bit. The 64-bit module is used instead when tracing 64-bit applications.
This approach is taken by the command-line kdb or the kernel debugger command, which needs to understand both the 32-bit and 64-bit layout of kernel data structures, depending upon whether the user is debugging a 32-bit or 64-bit kernel. It consists of a front-end and both a 32-bit and a 64-bit backend module. When invoked, it first tests the kernel mode—whether 32-bit or 64-bit—and then invokes the appropriate backend module. The problem with this approach is that it does not work well if both 32-bit and 64-bit applications have to be simultaneously traced in a single session.
A second known solution is to incorporate knowledge of application modes into the programming language and have a mode switch implemented as a new statement within the tracing program. For instance, if the same structure was being accessed by both the 32-bit and 64-bit application, the user could define a separate structure for each mode Such a solution could take the form of:                mode 32;        struct x32{ . . . };        mode 64;        struct x64{ . . . };        
This second solution thus would require the user to declare the structure twice with two different names. The first declaration would be built in 32-bit format, and the second declaration would be in 64-bit format. This double-declared solution allows the compiler to know about both possibilities simultaneously. The tracing program could then initiate a single appropriate tracing script dealing with both types of objects simultaneously. However, this double-declared solution would require the user to check whether the current process is running in 32-bit mode or 64-bit made within the tracing program and then choose the appropriate structure. Similar considerations would apply when using operators like sizeof( ) on variables.
Thus, the second solution requires the user to handle the 32-bit and 64-bit issue explicitly. All structures and variables for the structures and the actions associated with them must be duplicated for the 32-bit and the 64-bit scenarios. The user would need to know the exact mode of every application being probed. The tracing program would be quite complex and possibilities of errors are high. In the future, when a 32-bit application needs to be compiled into 64-bit mode, all existing tracing programs have to be updated to understand the new mode. Another problem with this solution is if “long long” fields are present in a 32-bit structure, which need to be cast into a pointer (that is treat the “long long” field as a pointer rather than as an integer), because pointers are only 4-bytes long while “long longs” are 8 bytes long, correct casting may require special language constructs to be employed by the user further complicating this solution.